# This is a youtube-dl youtube song downloader. It uses both youtube-dl
# for actual download and the invidious API for fetching info about the
# song. GPLv3+

import os
from subprocess import *
import json
import random
import urllib.request
import urllib.parse

from yt_dlp import YoutubeDL

dfolder = os.path.expanduser("~/.matrix_music")

youtubes = ["music.youtube.com", # That has to be in front, for strip function to work properly.
            "youtube.com",       # Since in there is "youtube.com" in "music.youtube.com"
            "youtu.be"]

invidious = ["invidious.namazso.eu",
             "yewtu.be",
             "invidious.snopyta.org",
             "invidious.kavin.rocks",
             "inv.riverside.rocks",
             "yt.artemislena.eu",
             "invidious.flokinet.to",
             "invidious.esmailelbob.xyz",
             "inv.bp.projectsegfau.lt",
             "y.com.sb",
             "invidious.tiekoetter.com",
             "invidious.nerdvpn.de",
             "invidious.slipfox.xyz",
             "watch.thekitty.zone",
             "inv.odyssey346.dev",
             "invidious.baczek.me",
             "invidious.weblibre.org",
             "invidious.privacydev.net",
             "yt.funami.tech"
             ]

other_links = ["c7hqkpkpemu6e7emz5b4vyz7idjgdvgaaa3dyimmeojqbgpea3xqjoid.onion",
               "w6ijuptxiku4xpnnaetxvnkc5vqcdu7mgns2u77qefoixi63vbvnpnqd.onion",
               "kbjggqkzv65ivcqj6bumvp337z6264huv5kpkwuv6gu5yjiskvan7fad.onion",
               "grwp24hodrefzvjjuccrkw3mjq4tzhaaq32amf33dzpmuxe7ilepcmad.onion",
               "osbivz6guyeahrwp2lnwyjk2xos342h4ocsxyqrlaopqjuhwn2djiiyd.onion",
               "u2cvlit75owumwpy4dj2hsmvkq7nvrclkpht7xgyye2pyoxhpmclkrad.onion",
               "euxxcnhsynwmfidvhjf6uzptsmh4dipkmgdmcmxxuo7tunp3ad2jrwyd.onion",
               "invidious.esmail5pdn24shtvieloeedh7ehz3nrwcdivnfhfcedl7gf4kwddhkqd.onion",
               "inv.vernccvbvyi5qhfzyqengccj7lkove6bjot2xhh5kajhwvidqafczrad.onion",
               "am74vkcrjp2d5v36lcdqgsj2m6x36tbrkhsruoegwfcizzabnfgf5zyd.onion",
               "ng27owmagn5amdm7l5s3rsqxwscl5ynppnis5dqcasogkyxcfqn7psid.onion",
               "iv.odysfvr23q5wgt7i456o5t3trw2cw5dgn56vbjfbq2m7xsc5vqbqpcyd.onion",
               "invidious.g4c3eya4clenolymqbpgwz3q3tawoxw56yhzk4vugqrl6dtu3ejvhjid.onion",
               "verni6dr4qxjgjumnvesxerh5rvhv6oy5ddeibaqy5d7tgbiiyfa.b32.i2p",
               "vid.priv.au",
               "invidious.lidarshield.cloud",
               "invidious.silur.me",
               "iv.melmac.space",
               "iv.ggtyler.dev",
               "invidious.epicsite.xyz",
               "invidious.sethforprivacy.com",
               "yt.oelrichsgarcia.de",
               "invidious.drivet.xyz",
               "invidious.dhusch.de",
               "inv.vern.cc",
               "vid.puffyan.us"
               "piped.kavin.rocks",
               "viewtube.io"]
    
all_instances = []
for t in [youtubes, invidious, other_links]:
    for i in t:
        all_instances.append(i)

class Logger(object):
    def debug(self, msg):
        pass

    def warning(self, msg):
        pass

    def error(self, msg):
        pass

ydl_opts = {'logger': Logger()}


def difference(x,y):
    if x > y:
        return x - y
    else:
        return y - x

def fetch_playlist(playlistId):

    try:

        try:
            url = "https://invidious.namazso.eu/api/v1/playlists/"+playlistId
            req = urllib.request.Request(url, data=None)
            f = urllib.request.urlopen(req)
            data = json.loads(f.read().decode('utf-8'))
        except:

            with YoutubeDL(ydl_opts) as ydl:
                dump = ydl.extract_info(url=playlistId, download=False)

                with open('dump.json', 'w') as fp:
                    json.dump(dump, fp, indent=4)

                # Converting youtube_dl data into Invidious type object
                data = {"videos":[]}
                for video in dump.get("entries", []):
                    data["videos"].append({"videoId":video.get("id", "")})

                
        return data

    
    except Exception as e:
        print("        [  \033[31mFAILED\033[00m  ] youtube_dl |", e)
        return str(e)
    
    
def fetch_info(videoId):

    try:

        with YoutubeDL(ydl_opts) as ydl:
            dump = ydl.extract_info(url=videoId, download=False)

            with open('dump.json', 'w') as fp:
                json.dump(dump, fp, indent=4)

            # Converting youtube_dl data into Invidious type object
            data = {"lengthSeconds":dump.get("duration", 999999999999999),
                    "title":dump.get("title", ""),
                    "author":dump.get("channel", "")}
                
        return data

    
    except Exception as e:
        print("        [  \033[31mFAILED\033[00m  ] youtube_dl |", e)
    
    for instance in invidious:
        try:
            url = "https://"+instance+"/api/v1/videos/"+videoId
            req = urllib.request.Request(url, data=None)
            f = urllib.request.urlopen(req)
            data = json.loads(f.read().decode('utf-8'))
            return data
        except Exception as e:
            print("        [  \033[31mFAILED\033[00m  ] "+instance+" |", e)
    
    

def search(query):

    # This function will search on youtube a query.
    for instance in invidious:
        try:
            url = "https://"+instance+"/api/v1/search/?q="+urllib.parse.quote_plus(query)
            req = urllib.request.Request(url, data=None)
            f = urllib.request.urlopen(req)
            data = json.loads(f.read().decode('utf-8'))
            return data
        except Exception as e:
            print("        [  \033[31mFAILED\033[00m  ] "+instance+" |", e)

       

def try_download(videoId, filename):
    
    # This fucntion will try download the song via youtube-dl

    #print("Trying to download", videoId)
    
    # Making sure that the download folder exists
    try:
        os.mkdir(dfolder)
    except:
        pass
    
    try:
        # We are using subbrocess.check_output here to catch errors.
        check_output(["yt-dlp",
                     "https://youtube.com/watch?v="+videoId,
                      "-x",
                     "--output",
                      dfolder+"/"+str(filename)+".%(ext)s"])
        for i in os.listdir(dfolder):
            if i.startswith(filename):
                return dfolder+"/"+i
        return dfolder+"/"+str(filename)
    
    except Exception as e:
        print("        [  \033[31mFAILED\033[00m  ] youtube_dl |", e)
        return False

def insure_song(videoId, filename):

    # This function will insure a song is downloaded.

    songInfo = fetch_info(videoId)

    #print("Song", songInfo["title"], "by", songInfo["author"], "direct attempt.")
    
    # First we will try downloading the song as is.
    d = try_download(videoId, filename)
    if d:
        #print("Worked!")
        return d
    else:
        #print("Failed! Trying similar...")
        
        # If it failed we are going to try downloading a similar song.

        titleblacklist = ["cover", "react", "review", "remix", "acoustic", "teaser", "live"]

        query = songInfo["title"]
        if " - Topic" in songInfo["author"]:
            query = songInfo["author"].replace(" - Topic", "")+" "+query
        
        others = search(query)

        for song in others:

            # Chekcing that it's a video
            if song["type"] != "video":
                continue
            
            # Checking if the song is a cover or a remix
            
            cont = False
            for i in titleblacklist:
                if i.lower() in song["title"].lower():
                    cont = i
                    break
            if cont:
                #print("Skipped:", song["title"], "because", cont )
                continue

            # Chekcing the length of the song to be roughly the original

            if difference(song["lengthSeconds"], songInfo["lengthSeconds"]) > 60:
                continue

            #print("Trying to download", song["title"])
            
            d =  try_download(song["videoId"], filename)
            if d:
                return d

        return False


def strip(url):

    """Function that strips youtube links. And leaves only the videoId."""
    
    videoId = url

    # Maybe it's a playlist
    if "list=" in videoId and "/watch?v=" not in videoId:
        videoId = videoId[videoId.find("list=")+5:]

    
    videoId = videoId.replace("http://", "").replace("https://", "").replace("www.", "")
    for i in all_instances:
        videoId = videoId.replace(i, "")
    videoId = videoId.replace("/watch?v=", "")
    if "&" in videoId:
        videoId = videoId[:videoId.find("&")]

    videoId = videoId.replace("/", "")
        
    return videoId
